use std::io;
use std::net;
use std::net::ToSocketAddrs;

use argh::FromArgs;

use aspect_core::datadir;
use aspect_proto::core;

use crate::context;
use crate::errors::Error;
use crate::util;

#[derive(Debug, PartialEq, FromArgs)]
#[argh(
    subcommand,
    name = "init",
    description = "init a new device identity under a new user, registering with a homeserver"
)]
pub struct Args {
    #[argh(positional, description = "username")]
    username: String,

    #[argh(positional, description = "homeserver socket addr")]
    homeserver: String,

    #[argh(positional, description = "the device identity name")]
    name: String,

    #[argh(
        option,
        short = 'p',
        description = "homeserver port (default 5000)",
        default = "5000"
    )]
    port: u16,
}

pub fn exec(args: Args, ctx: context::Context) -> Result<(), Error> {
    eprintln!("using homeserver: {}:{}", args.homeserver, args.port);

    eprint!("password: ");
    let password = {
        let mut stdin_lines = io::stdin().lines();
        let pw = stdin_lines.next();
        match pw {
            Some(Ok(s)) => {
                let trimmed = s.trim();
                if trimmed.is_empty() {
                    eprintln!("password not provided, exiting");
                    return Ok(());
                }
                trimmed.to_owned()
            }
            _ => {
                eprintln!("could not read password, exiting");
                return Ok(()); // TODO make error
            }
        }
    };

    let userdata = datadir::UserData {
        username: args.username,
        password,
        homeserver_host: args.homeserver,
        homeserver_port: args.port,
    };

    let sb = aspect_ident::SignerBuilder::new(args.name);
    let (signer, dp) = sb.finalize()?;
    let ident = dp.last_update().ident();

    let dp_buf = aspect_codec::encode_to_vec(&dp)?;
    let dp_hex = hex::encode(dp_buf);

    // Submit the registration.
    let Some(addr) = resolve_addr(&userdata.homeserver_host, userdata.homeserver_port)? else {
        eprintln!("can't resolve homeserver hostname");
        return Err(Error::Unimplemented);
    };

    let rt = util::make_rt();
    let res: Result<(), Error> = rt.block_on(async {
        let conn = ecksport_net::builder::ClientBuilder::new(aspect_proto::protocol::PROTO_V0)
            .connect_tcp(addr)
            .await
            .map_err(|e| Error::Other(e.to_string()))?;
        let rpc_client = ecksport_rpc::client::RpcClient::new(conn);
        let hsc = core::HomeserverClient::from_client(rpc_client);
        eprintln!("submitting registration...");
        hsc.register_user(userdata.username.clone(), userdata.password.clone())
            .await?;
        eprintln!("OK (registration)");

        eprintln!("submitting device association...");
        hsc.register_device(
            userdata.username.clone(),
            userdata.password.clone(),
            dp.clone(),
        )
        .await?;
        eprintln!("OK (association)");

        let pkgs = hsc.get_user_devices(userdata.username.clone()).await?;
        eprintln!("devices:");
        for pkg in pkgs {
            let ident = pkg.last_update().ident();
            eprintln!("* {ident}");
        }

        Ok(())
    });

    // Do this separately because of some reason?
    res?;

    // Finally, now that we know that we've registered, we can write the datadir.
    let _dd = datadir::Datadir::create(ctx.datadir, &userdata, &signer)?;

    eprintln!("saved datadir");
    println!("username {}", userdata.username);
    println!("ident {ident}");
    println!("dirpackage {dp_hex}");
    Ok(())
}

fn resolve_addr(host: &str, port: u16) -> Result<Option<net::SocketAddr>, io::Error> {
    Ok((host, port).to_socket_addrs()?.next())
}
